package com.suxin.utils;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.Set;
import java.util.function.*;
import java.util.stream.Collector;

/**
 * @author Tang
 * @classname CollectorsPlus
 * @description [  ]
 * @date 2023/3/10 17:21
 */
public class CollectorsPlus {

    private static final Set<Collector.Characteristics> CH_NOID = Collections.emptySet();

    /**
     * 解决 double 丢失精度问题，用 BigDecimal 计算
     * @param mapper
     * @param <T>
     * @return
     */
    public static <T> Collector<T, ?, Double> summingBigDecimal(ToBigDecimalFunction<? super T> mapper) {
        return new CollectorImpl<>(
                () -> new BigDecimal[]{BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO},
                (a, t) -> {
                        sumWithCompensation(a, mapper.applyAsBigDecimal(t));
                        a[2] = a[2].add(mapper.applyAsBigDecimal(t));
                    },
                (a, b) -> {
                        sumWithCompensation(a, b[0]);
                        a[2] = a[2].add(b[2]);
                        return sumWithCompensation(a, b[1]);
                    },
                CollectorsPlus::computeFinalSum,
                CH_NOID
        );
    }

    /**
     * double to BigDecimal
     * @param mapper
     * @param <T>
     * @return
     */
    public static <T> Collector<T, ?, Double> summingDoubleToBigDecimal(ToDoubleFunction<? super T> mapper) {
        ToBigDecimalFunction<T> bigDecimalFunction = (item) -> new BigDecimal(String.valueOf(mapper.applyAsDouble(item)));
        return summingBigDecimal(bigDecimalFunction);
    }


    private static BigDecimal[] sumWithCompensation(BigDecimal[] intermediateSum, BigDecimal value) {
        BigDecimal tmp = value.subtract(intermediateSum[1]);
        BigDecimal sum = intermediateSum[0];
        BigDecimal velvel = sum.add(tmp);
        intermediateSum[1] = (velvel.subtract(sum)).subtract(tmp);
        intermediateSum[0] = velvel;
        return intermediateSum;
    }

    private static Double computeFinalSum(BigDecimal[] summands) {
        BigDecimal tmp = summands[0].add(summands[1]);
        return tmp.doubleValue();
    }

    private static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
        private final Supplier<A> supplier;
        private final BiConsumer<A, T> accumulator;
        private final BinaryOperator<A> combiner;
        private final Function<A, R> finisher;
        private final Set<Characteristics> characteristics;

        CollectorImpl(Supplier<A> supplier,
                      BiConsumer<A, T> accumulator,
                      BinaryOperator<A> combiner,
                      Function<A,R> finisher,
                      Set<Characteristics> characteristics) {
            this.supplier = supplier;
            this.accumulator = accumulator;
            this.combiner = combiner;
            this.finisher = finisher;
            this.characteristics = characteristics;
        }

        @Override
        public BiConsumer<A, T> accumulator() {
            return accumulator;
        }

        @Override
        public Supplier<A> supplier() {
            return supplier;
        }

        @Override
        public BinaryOperator<A> combiner() {
            return combiner;
        }

        @Override
        public Function<A, R> finisher() {
            return finisher;
        }

        @Override
        public Set<Characteristics> characteristics() {
            return characteristics;
        }
    }

}
